﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Common;
using System.Data.SqlClient;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Services;
using JPR.TunelProxy.Common;

namespace JPR.TunelProxy.WebUi
{
    /// <summary>
    /// Summary description for $codebehindclassname$
    /// </summary>
    [WebService(Namespace = "http://pospa.net/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class Listener : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            int result = -1;
            string source = string.Format("Host address: {0}; Host name: {1}; Agent: {2}",context.Request.UserHostAddress, context.Request.UserHostName, context.Request.UserAgent), destination = string.Empty;
            Logger.WriteTrace("Web request for server side proxy recieved.", TraceLevel.Verbose);
            using (TcpClient client = new TcpClient())
            using (Stream outputStream = context.Response.OutputStream)
            using (GZipStream gZipOutputStream = new GZipStream(outputStream, CompressionMode.Compress, true))
            using (BinaryWriter outputWriter = new BinaryWriter(gZipOutputStream))
            {
                try
                {
                    context.Response.ContentType = "application/octet-stream";
                    Rijndael crypto = Rijndael.Create();
                    crypto.Padding = PaddingMode.None;
                    string transferEncoding;
                    using (Stream inputStream = context.Request.InputStream)
                    using (
                        CryptoStream cryptoStream =
                            new CryptoStream(inputStream,
                                             crypto.CreateDecryptor(
                                                 Encoding.UTF8.GetBytes(ConfigurationManager.AppSettings["CryptoKey"]),
                                                 crypto.IV), CryptoStreamMode.Read))
                    using (GZipStream gZipStream = new GZipStream(inputStream, CompressionMode.Decompress, true))
                    using (BinaryReader reader = new BinaryReader(gZipStream))
                    {
                        #region Address parseing

                        string line = reader.ReadLine();
                        //if (string.IsNullOrEmpty(line))
                        //{
                        //    line = reader.ReadLine();
                        //}
                        string address = destination = line.Split(' ')[1];

                        Regex regex = new Regex(@"^(http://)?(?<host>[^/:]+)(:?(?<port>\d+))?((?<path>/.*))?$");
                        Match match = regex.Match(address);

                        Logger.WriteTrace("Address parsed.", TraceLevel.Verbose,
                                          new[] {new KeyValuePair<string, object>("Address", address)});

                        int port;
                        if (string.IsNullOrEmpty(match.Groups["port"].Value))
                        {
                            port = 80;
                        }
                        else
                        {
                            port = int.Parse(match.Groups["port"].Value);
                        }

                        #endregion Address parseing

                        client.Connect(match.Groups["host"].Value, port);
                        using (NetworkStream networkStream = client.GetStream())
                        using (BinaryReader networkReader = new BinaryReader(networkStream))
                        using (BinaryWriter networkWriter = new BinaryWriter(networkStream))
                        {
                            networkWriter.WriteLine(line);

                            #region Header
                            int contentLength = 0;
                            transferEncoding = string.Empty;
                            while (!string.IsNullOrEmpty(line))
                            {
                                string header = line.Trim().Split(':')[0];
                                string headerValue = line.Trim().Split(':').Length > 1
                                                         ? line.Trim().Split(':')[1].Trim()
                                                         : string.Empty;
                                if (header.Equals("Content-Length"))
                                {
                                    contentLength = int.Parse(headerValue);
                                }
                                if (header.Equals("Transfer-Encoding"))
                                {
                                    transferEncoding = headerValue;
                                }
                                if (!header.ToLower().Contains("connection") && !header.ToLower().Contains("alive"))
                                {
                                    networkWriter.WriteLine(line);
                                }
                                line = reader.ReadLine();
                            }
                            networkWriter.WriteLine(string.Empty);
                            #endregion Header
                            #region Content
                            if (contentLength > 0)
                            {
                                byte[] buff = new byte[contentLength];
                                int counter = 0;
                                do
                                {
                                    int i = reader.Read(buff, 0, buff.Length);
                                    counter += i;
                                    networkWriter.Write(buff, 0, i);
                                } while (counter < contentLength);
                            }
                            if (transferEncoding.ToLower().Equals("chunked"))
                            {
                                int chunkSize;
                                do
                                {
                                    string xLine = reader.ReadLine();
                                    networkWriter.WriteLine(xLine);
                                    if (string.IsNullOrEmpty(xLine))
                                    {
                                        xLine = reader.ReadLine();
                                        networkWriter.WriteLine(xLine);
                                    }
                                    chunkSize = int.Parse(xLine, NumberStyles.HexNumber);
                                    int subCounter = 0;
                                    byte[] buffer = new byte[chunkSize];
                                    while (subCounter < chunkSize)
                                    {
                                        int countReaded = reader.Read(buffer, 0, chunkSize - subCounter);
                                        subCounter += countReaded;
                                        networkWriter.Write(buffer, 0, countReaded);
                                    }
                                } while (chunkSize > 0);
                            }
                            #endregion Content

                            networkWriter.Flush();
                            networkStream.Flush();

                            Logger.WriteTrace("Server side web request sent.", TraceLevel.Verbose);

                            contentLength = 0;
                            transferEncoding = string.Empty;

                            #region Header

                            string responceLine = networkReader.ReadLine();
                            while (!string.IsNullOrEmpty(responceLine))
                            {
                                string header = responceLine.Trim().Split(':')[0];
                                string headerValue = responceLine.Trim().Split(':').Length > 1
                                                         ? responceLine.Trim().Split(':')[1].Trim()
                                                         : string.Empty;
                                if (header.Equals("Content-Length"))
                                {
                                    contentLength = int.Parse(headerValue);
                                }
                                if (header.Equals("Transfer-Encoding"))
                                {
                                    transferEncoding = headerValue;
                                }
                                outputWriter.WriteLine(responceLine);
                                responceLine = networkReader.ReadLine();
                            }
                            outputWriter.WriteLine(responceLine);

                            #endregion Header

                            #region Chunked content

                            if (transferEncoding.ToLower().Equals("chunked"))
                            {
                                int chunkSize;
                                do
                                {
                                    string xLine = networkReader.ReadLine();
                                    outputWriter.WriteLine(xLine);
                                    if (string.IsNullOrEmpty(xLine))
                                    {
                                        xLine = networkReader.ReadLine();
                                        outputWriter.WriteLine(xLine);
                                    }
                                    chunkSize = int.Parse(xLine, NumberStyles.HexNumber);
                                    int subCounter = 0;
                                    byte[] buffer = new byte[chunkSize];
                                    while (subCounter < chunkSize)
                                    {
                                        int countReaded = networkReader.Read(buffer, 0, chunkSize - subCounter);
                                        subCounter += countReaded;
                                        outputWriter.Write(buffer, 0, countReaded);
                                    }
                                } while (chunkSize > 0);
                            }

                            #endregion Chunked content

                            #region Standard content

                            if (contentLength > 0)
                            {
                                int counter = 0;
                                byte[] buffer = new byte[contentLength];
                                while (counter < contentLength)
                                {
                                    int i = networkReader.Read(buffer, 0, contentLength - counter);
                                    counter += i;
                                    outputWriter.Write(buffer, 0, i);
                                }
                            }

                            #endregion Standard content

                            outputWriter.Flush();
                            gZipOutputStream.Flush();
                            outputStream.Flush();
                           
                            result = 0;

                            Logger.WriteTrace("Server side web responce sent.", TraceLevel.Verbose);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Logger.WriteException(ex);
                    result = -1;
                }
                finally
                {
                    try
                    {
                        Database db = new Microsoft.Practices.EnterpriseLibrary.Data.Sql.SqlDatabase(ConfigurationManager.ConnectionStrings["AccessLog"].ConnectionString);
                        using (DbConnection connection=db.CreateConnection())
                        using (DbCommand command = db.GetStoredProcCommand("dbo.Proxy_WriteAccessLog"))
                        {
                            command.Connection = connection;
                            DbParameter parameter;
                            parameter = new SqlParameter("@Source", source);
                            command.Parameters.Add(parameter);
                            parameter = new SqlParameter("@Destination", destination);
                            command.Parameters.Add(parameter);
                            parameter = new SqlParameter("@Result", result);
                            command.Parameters.Add(parameter);
                            connection.Open();
                            int res =command.ExecuteNonQuery();
                            connection.Close();
                        }
                    }
                    catch (Exception ex)
                    {
                        Logger.WriteException(ex);
                    }
                }
            }
        }

        public bool IsReusable
        {
            get { return false; }
        }
    }
}